import 'dart:math' as math;

import 'package:vector_math/vector_math.dart';
import 'package:vector_math_test/src/test_utils.dart';

import '../common/test_page.dart';

class RayTestPage extends TestPage{
  RayTestPage(super.title) {
    group('Ray', () {
      test('Ray.originDirection().at(double t)和setZero()', testRayAt);
      test('Ray.originDirection().intersectsWithSphere(Sphere other)', testRayIntersectionSphere);
      test('Ray.originDirection().intersectsWithTriangle(Triangle other)', testRayIntersectionTriangle);
      test('Ray.originDirection().intersectsWithAabb3(Aabb3 other)', testRayIntersectionAabb3);
    });
  }

  void testRayAt() {
    final parent = Ray.originDirection($v3(1.0, 1.0, 1.0), $v3(-1.0, 1.0, 1.0));

    final atOrigin = parent.at(0.0);
    final atPositive = parent.at(1.0);
    final atNegative = parent.at(-2.0);

    expect(atOrigin.x, 1.0);
    expect(atOrigin.y, 1.0);
    expect(atOrigin.z, 1.0);
    expect(atPositive.x, 0.0);
    expect(atPositive.y, 2.0);
    expect(atPositive.z, 2.0);
    expect(atNegative.x, 3.0);
    expect(atNegative.y, -1.0);
    expect(atNegative.z, -1.0);

    atOrigin.setZero();
    atPositive.setZero();
    atNegative.setZero();

    parent.copyAt(atOrigin, 0.0);
    parent.copyAt(atPositive, 1.0);
    parent.copyAt(atNegative, -2.0);

    expect(atOrigin.x, 1.0);
    expect(atOrigin.y, 1.0);
    expect(atOrigin.z, 1.0);
    expect(atPositive.x, 0.0);
    expect(atPositive.y, 2.0);
    expect(atPositive.z, 2.0);
    expect(atNegative.x, 3.0);
    expect(atNegative.y, -1.0);
    expect(atNegative.z, -1.0);
  }

  void testRayIntersectionSphere() {
    final parent = Ray.originDirection($v3(1.0, 1.0, 1.0), $v3(0.0, 1.0, 0.0));
    final inside = Sphere.centerRadius($v3(2.0, 1.0, 1.0), 2.0);
    final hitting = Sphere.centerRadius($v3(2.5, 4.5, 1.0), 2.0);
    final cutting = Sphere.centerRadius($v3(0.0, 5.0, 1.0), 1.0);
    final outside = Sphere.centerRadius($v3(-2.5, 1.0, 1.0), 1.0);
    final behind = Sphere.centerRadius($v3(1.0, -1.0, 1.0), 1.0);

    expect(parent.intersectsWithSphere(inside), math.sqrt(3.0));
    expect(parent.intersectsWithSphere(hitting), 3.5 - math.sqrt(1.75));
    expect(parent.intersectsWithSphere(cutting), 4.0);
    expect(parent.intersectsWithSphere(outside), null);
    expect(parent.intersectsWithSphere(behind), null);
  }

  void testRayIntersectionTriangle() {
    final parent = Ray.originDirection($v3(1.0, 1.0, 1.0), $v3(0.0, 1.0, 0.0));
    final hitting = Triangle.points(
        $v3(2.0, 2.0, 0.0), $v3(0.0, 4.0, -1.0), $v3(0.0, 4.0, 3.0));
    final cutting = Triangle.points(
        $v3(0.0, 1.5, 1.0), $v3(2.0, 1.5, 1.0), $v3(1.0, 1.5, 3.0));
    final outside = Triangle.points(
        $v3(2.0, 2.0, 0.0), $v3(2.0, 6.0, 0.0), $v3(2.0, 2.0, 3.0));
    final behind = Triangle.points(
        $v3(0.0, 0.0, 0.0), $v3(0.0, 3.0, 0.0), $v3(0.0, 3.0, 4.0));

    absoluteTest(parent.intersectsWithTriangle(hitting), 2.0);
    absoluteTest(parent.intersectsWithTriangle(cutting), 0.5);
    expect(parent.intersectsWithTriangle(outside), null);
    expect(parent.intersectsWithTriangle(behind), null);

    // Test cases from real-world failures:
    // Just barely intersects, but gets rounded out
    final p2 = Ray.originDirection(
        $v3(0.0, -0.16833500564098358, 0.7677000164985657),
        $v3(-0.0, -0.8124330043792725, -0.5829949975013733));
    final t2 = Triangle.points(
        $v3(0.03430179879069328, -0.7268069982528687, 0.3532710075378418),
        $v3(0.0, -0.7817990183830261, 0.3641969859600067),
        $v3(0.0, -0.7293699979782104, 0.3516849875450134));
    expect(p2.intersectsWithTriangle(t2), '');
    // Ray is not quite perpendicular to triangle, but gets rounded out
    final p3 = Ray.originDirection(
        $v3(0.023712199181318283, -0.15045200288295746, 0.7751160264015198),
        $v3(0.6024960279464722, -0.739005982875824, -0.3013699948787689));
    final t3 = Triangle.points(
        $v3(0.16174300014972687, -0.3446039855480194, 0.7121580243110657),
        $v3(0.1857299953699112, -0.3468630015850067, 0.6926270127296448),
        $v3(0.18045000731945038, -0.3193660080432892, 0.6921690106391907));
    expect(p3.intersectsWithTriangle(t3), '');
  }

  void testRayIntersectionAabb3() {
    final parent = Ray.originDirection($v3(1.0, 1.0, 1.0), $v3(0.0, 1.0, 0.0));
    final hitting = Aabb3.minMax($v3(0.5, 3.5, -10.0), $v3(2.5, 5.5, 10.0));
    final cutting = Aabb3.minMax($v3(0.0, 2.0, 1.0), $v3(2.0, 3.0, 2.0));
    final outside = Aabb3.minMax($v3(2.0, 0.0, 0.0), $v3(6.0, 6.0, 6.0));
    final behind = Aabb3.minMax($v3(0.0, -2.0, 0.0), $v3(2.0, 0.0, 2.0));
    final inside = Aabb3.minMax($v3(0.0, 0.0, 0.0), $v3(2.0, 2.0, 2.0));

    expect(parent.intersectsWithAabb3(hitting), 2.5);
    expect(parent.intersectsWithAabb3(cutting), 1.0);
    expect(parent.intersectsWithAabb3(outside), null);
    expect(parent.intersectsWithAabb3(behind), null);
    expect(parent.intersectsWithAabb3(inside), -1.0);
  }

}